﻿using System;
using System.Diagnostics;
using RaytracerLib.MathLib;
using YAXLib;
using RaytracerLib.Rays;
using RaytracerLib.Materials;
using System.Collections.Generic;
using System.Linq;
using RaytracerLib.UVMappers;

namespace RaytracerLib.RenderableObjects
{
    public abstract class RenderableObject : OrientedElement
    {
        [YAXNode]
        public readonly RenderableObjectMaterials Materials = new RenderableObjectMaterials();

        [YAXNode]
        public bool OneSide;

        [YAXNode]
        public bool Closed;

        [YAXNode("UVMapper")]
        protected UVMapper m_uv_mapper;

        protected RenderableObject(Vector3 a_right, Vector3 a_up) :
            base(a_right, a_up)
        {
        }

        public UVMapper UVMapper
        {
            get
            {
                return m_uv_mapper;
            }
            set
            {
                m_uv_mapper = value;
                Update(UpdateFlags.UVMapper);
            }
        }

        [YAXOnDeserialized]
        private void OnDeserialized(Scene a_scene)
        {
            Scene = a_scene;
        }

        /// <summary>
        /// Do not use directly. Use Intersection.UVW.
        /// </summary>
        /// <param name="a_intersection"></param>
        /// <returns></returns>
        internal virtual Vector3 GetUVW(Intersection a_intersection)
        {
            return a_intersection.LocalPos;
        }

        /// <summary>
        /// Intersection position in local coords.
        /// </summary>
        /// <param name="a_intersection"></param>
        /// <returns></returns>
        internal Vector3 GetLocalPos(Intersection a_intersection)
        {
            return WorldToLocal * a_intersection.Pos;
        }

        public Material DefaultMaterial
        {
            get
            {
                return Materials[0];
            }
            set
            {
                Materials[0] = value;
            }
        }

        public override Scene Scene
        {
            get
            {
                return base.Scene;
            }
            internal set
            {
                if (Object.ReferenceEquals(Scene, value))
                    return;

                if (value == null)
                {
                    if (Scene != null)
                        Debug.Assert(!Scene.RenderableObjects.Contains(this));
                }
                else
                {
                    if (Scene != null)
                        Debug.Assert(!Scene.RenderableObjects.Contains(this));
                    Debug.Assert(value.RenderableObjects.Contains(this));
                }

                Materials.Scene = value;

                base.Scene = value;
            }
        }

        protected override void RenderStart(RenderStartPhase a_phase)
        {
            base.RenderStart(a_phase);

            if (a_phase == RenderStartPhase.CheckForErrors)
            {
                if (!Closed && DefaultMaterial.HasRefraction)
                    throw new InvalidOperationException();
                if (!Closed && DefaultMaterial.HasAttenuation)
                    throw new InvalidOperationException();
                if (Materials.Select(m => m.RefractionIndice).Distinct().Count() != 1)
                    throw new InvalidOperationException();
                if (Materials.Select(m => m.Attenuation).Distinct().Count() != 1)
                    throw new InvalidOperationException();
            }
        }

        /// <summary>
        /// Do not use directly. Use Intersection.ShadeNormal.
        /// </summary>
        /// <param name="a_intersection"></param>
        /// <returns></returns>
        internal virtual Vector3 GetShadeNormal(Intersection a_intersection)
        {
            return a_intersection.Normal;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="a_intersection"></param>
        /// <param name="a_tx">Texture space. Gradient X vector.</param>
        /// <param name="a_ty">Texture space. Gradient Y vector.</param>
        internal void GetTangents(Intersection a_intersection,
            out Vector2 a_tx, out Vector2 a_ty)
        {
            if (a_intersection.BackHit)
            {
                a_tx = -Vector2.XAXIS;
                a_ty = Vector2.YAXIS;
            }
            else
            {
                a_tx = Vector2.XAXIS;
                a_ty = -Vector2.YAXIS;
            }
        }

        public override bool Remove()
        {
            return Scene.RenderableObjects.Remove(this);
        }

        public override string Name
        {
            get
            {
                return base.Name;
            }
            set
            {
                if (Name == value)
                    return;

                if (Scene != null)
                    if (Scene.RenderableObjects[value] != null)
                        throw new InvalidOperationException();

                base.Name = value;
            }
        }

        /// <summary>
        /// Do not use directly. Consider using Intersection.UV.
        /// </summary>
        /// <param name="a_intersection"></param>
        /// <returns></returns>
        internal virtual Vector2 GetUV(Intersection a_intersection)
        {
            return m_uv_mapper.Map(a_intersection.UVW);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="a_intersection"></param>
        /// <param name="a_tangent_x">World space. Texture X gradient direction.</param>
        /// <param name="a_tangent_y">World space. Texture Y gradient direction.</param>
        internal abstract void GetTangents(Intersection a_intersection,
            out Vector3 a_tangent_x, out Vector3 a_tangent_y);

        internal abstract Intersection GetIntersection(Intersection a_source_ray_intersection, Ray a_ray);

        /// <summary>
        /// Do not use directly. Consider using Intersection.Normal.
        /// </summary>
        /// <param name="a_intersection"></param>
        /// <returns></returns>
        internal abstract Vector3 GetNormal(Intersection a_intersection);
    }
}